/**
* @file utils.cpp
*
* Copyright (C) 2020. Huawei Technologies Co., Ltd. All rights reserved.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
*/
#include "utils.h"
#include "atlas_utils.h"


bool Utils::WriteToFile(FILE *fileFp, void *dataDev, uint32_t dataSize)
{
    if (fileFp == nullptr) {
        ERROR_LOG("fileFp is nullptr!");
        return false;
    }

    if (dataDev == nullptr) {
        ERROR_LOG("dataDev is nullptr!");
        return false;
    }

    // copy output to host memory
    void *data = nullptr;
    aclrtRunMode runMode;
    aclrtGetRunMode(&runMode);
    aclError aclRet;
    if (runMode == ACL_HOST) {
        data = malloc(dataSize);
        if (data == nullptr) {
            ERROR_LOG("malloc host data buffer failed. dataSize=%u\n", dataSize);
            return false;
        }
        aclRet = aclrtMemcpy(data, dataSize, dataDev, dataSize, ACL_MEMCPY_DEVICE_TO_HOST);
        if (aclRet != ACL_ERROR_NONE) {
            ERROR_LOG("acl memcpy data to host failed, dataSize=%u, ret=%d.\n", dataSize, aclRet);
            free(data);
            return false;
        }
    } else {
        data = dataDev;
    }

    bool ret = true;
    size_t writeRet = fwrite(data, 1, dataSize, fileFp);
    if (writeRet != dataSize) {
        ERROR_LOG("need write %u bytes, but only write %zu bytes, error=%s.\n",
                      dataSize, writeRet, strerror(errno));
        ret = false;
    }

    if (runMode == ACL_HOST) {
        free(data);
    }
    fflush(fileFp);
    return ret;
}

Result Utils::CheckFolder(const char* foldName)
{
    if (access(foldName , 0) == -1) {
        int flag=mkdir(foldName , 0777);
        if (flag == 0) {
            INFO_LOG( "make directory successfully.");
        } else {
            ERROR_LOG( "make directory errorly.");
            return FAILED;
        }
    }
    return SUCCESS;
}

void* Utils::CopyDataDeviceToLocal(void* deviceData, uint32_t dataSize) {
    uint8_t* buffer = new uint8_t[dataSize];
    if (buffer == nullptr) {
        ERROR_LOG("New malloc memory failed");
        return nullptr;
    }

    aclError aclRet = aclrtMemcpy(buffer, dataSize, deviceData, dataSize, ACL_MEMCPY_DEVICE_TO_HOST);
    if (aclRet != ACL_ERROR_NONE) {
        ERROR_LOG("Copy device data to local failed, aclRet is %d", aclRet);
        delete[](buffer);
        return nullptr;
    }

    return (void*)buffer;
}

std::string Utils::printCurrentTime()
{
    struct timeval tv;
    struct timezone tz;
    struct tm* p = nullptr;

    gettimeofday(&tv, &tz);
    p = localtime(&tv.tv_sec);
    std::string pi = std::to_string(p->tm_year + 1900) + std::to_string(p->tm_mon + 1) + std::to_string(p->tm_mday) \
		     + "_" + std::to_string(p->tm_hour) + "_" + std::to_string(p->tm_min) + "_" + \
		     std::to_string(p->tm_sec) + "_" + std::to_string(tv.tv_usec); 
    return pi;
}

void* Utils::CopyDataHostToDvpp(void* data, int size) {
    void* buffer = nullptr;

    auto aclRet = acldvppMalloc(&buffer, size);
    if (aclRet != ACL_ERROR_NONE) {
        ERROR_LOG("acl malloc dvpp data failed, dataSize=%u, ret=%d",
        size, aclRet);
        return nullptr;
    }
    // INFO_LOG("malloc dvpp memory size %d ok", size);
    // copy input to device memory
    aclRet = aclrtMemcpy(buffer, size, data, size, ACL_MEMCPY_HOST_TO_DEVICE);
    if (aclRet != ACL_ERROR_NONE) {
        ERROR_LOG("acl memcpy data to dvpp failed, size %u, error %d", size, aclRet);
        acldvppFree(buffer);
        return nullptr;
    }
    INFO_LOG("copy data to dvpp ok");

    return buffer;
}

void* Utils::CopyDataDeviceToDvpp(void* data, int size) {
    void* buffer = nullptr;

    auto aclRet = acldvppMalloc(&buffer, size);
    if (aclRet != ACL_ERROR_NONE) {
        ERROR_LOG("acl malloc dvpp data failed, dataSize=%u, ret=%d",
        size, aclRet);
        return nullptr;
    }
    // INFO_LOG("malloc dvpp memory size %d ok", size);
    // copy input to device memory
    aclRet = aclrtMemcpy(buffer, size, data, size, ACL_MEMCPY_DEVICE_TO_DEVICE);
    if (aclRet != ACL_ERROR_NONE) {
        ERROR_LOG("acl memcpy data to dvpp failed, size %u, error %d", size, aclRet);
        acldvppFree(buffer);
        return nullptr;
    }
    // INFO_LOG("copy data to dvpp ok");

    return buffer;
}

Result Utils::CopyImageDataToDvpp(ImageData& imageDevice, cv::Mat& srcImage) {
    aclrtRunMode runMode_;
    aclError ret = aclrtGetRunMode(&runMode_);
    if (ret != ACL_ERROR_NONE) {
        ERROR_LOG("acl get run mode failed");
        return FAILED;
    }

    void* buffer = nullptr;
    if (runMode_ == ACL_HOST){
        buffer = CopyDataHostToDvpp(srcImage.data, srcImage.rows * srcImage.cols * srcImage.elemSize());
        if (buffer == nullptr) {
            ERROR_LOG("Copy image to device failed");
            return FAILED;
        }
    }
    else{
        buffer = CopyDataDeviceToDvpp(srcImage.data, srcImage.rows * srcImage.cols * srcImage.elemSize());
        if (buffer == nullptr) {
            ERROR_LOG("Copy image to device failed");
            return FAILED;
        }
    }

    imageDevice.width = srcImage.cols;
    imageDevice.height = srcImage.rows;
    imageDevice.size =  srcImage.rows * srcImage.cols * srcImage.elemSize();
    imageDevice.data.reset((uint8_t*)buffer, [](uint8_t* p) { acldvppFree((void *)p); });
    return SUCCESS;
}

Result Utils::CopyImageDataToDvpp(ImageData& imageDevice, ImageData srcImage) {
    aclrtRunMode runMode_;
    aclError ret = aclrtGetRunMode(&runMode_);
    if (ret != ACL_ERROR_NONE) {
        ERROR_LOG("acl get run mode failed");
        return FAILED;
    }

    void* buffer = nullptr;
    if (runMode_ == ACL_HOST){
        buffer = CopyDataHostToDvpp(srcImage.data.get(), srcImage.size);
        if (buffer == nullptr) {
            ERROR_LOG("Copy image to device failed");
            return FAILED;
        }
    }
    else{
        buffer = CopyDataDeviceToDvpp(srcImage.data.get(), srcImage.size);
        if (buffer == nullptr) {
            ERROR_LOG("Copy image to device failed");
            return FAILED;
        }
    }

    imageDevice.width = srcImage.width;
    imageDevice.height = srcImage.height;
    imageDevice.size = srcImage.size;
    imageDevice.data.reset((uint8_t*)buffer, [](uint8_t* p) { acldvppFree((void *)p); });
    return SUCCESS;
}

void* Utils::CopyDataToDevice(void* data, uint32_t dataSize, aclrtMemcpyKind policy) {
    void* buffer = nullptr;
    aclError aclRet = aclrtMalloc(&buffer, dataSize, ACL_MEM_MALLOC_HUGE_FIRST);
    if (aclRet != ACL_ERROR_NONE) {
        ERROR_LOG("malloc device data buffer failed, aclRet is %d", aclRet);
        return nullptr;
    }

    aclRet = aclrtMemcpy(buffer, dataSize, data, dataSize, policy);
    if (aclRet != ACL_ERROR_NONE) {
        ERROR_LOG("Copy data to device failed, aclRet is %d", aclRet);
        (void)aclrtFree(buffer);
        return nullptr;
    }

    return buffer;
}

void* Utils::CopyDataDeviceToDevice(void* deviceData, uint32_t dataSize) {
    return CopyDataToDevice(deviceData, dataSize, ACL_MEMCPY_DEVICE_TO_DEVICE);
}

void* Utils::CopyDataHostToDevice(void* deviceData, uint32_t dataSize) {
    return CopyDataToDevice(deviceData, dataSize, ACL_MEMCPY_HOST_TO_DEVICE);
}


unsigned short Utils::Crc16Fast(const unsigned char *ptr, unsigned len, unsigned short initvalue) {
    unsigned short crc = initvalue;
    unsigned char dat, tmp;
    
    while(len--)
    {
        tmp = *ptr++;
        dat = (crc>>12);
        crc <<= 4;
        crc ^= Crc16FastTable[dat ^ (tmp>>4)];
        dat = (crc>>12);
        crc <<= 4;
        crc ^= Crc16FastTable[dat ^ (tmp & 0x0F)];
    }
    return crc;
}


void swapYUV_I420toNV12(unsigned char* i420bytes, unsigned char* nv12bytes, int width, int height)
{
    int nLenY = width * height;
    int nLenU = nLenY / 4;

    memcpy(nv12bytes, i420bytes, width * height);

    for (int i = 0; i < nLenU; i++)
    {
        nv12bytes[nLenY + 2 * i] = i420bytes[nLenY + i];                    // U
        nv12bytes[nLenY + 2 * i + 1] = i420bytes[nLenY + nLenU + i];        // V
    }
}

void Utils::BGR2YUV_nv12(const cv::Mat& src, cv::Mat &dst)
{
    int w_img = src.cols;
    int h_img = src.rows;
    dst = cv::Mat(h_img*1.5, w_img, CV_8UC1, cv::Scalar(0));
    cv::Mat src_YUV_I420(h_img*1.5, w_img, CV_8UC1, cv::Scalar(0));  //YUV_I420
    cvtColor(src, src_YUV_I420, CV_BGR2YUV_I420);
    swapYUV_I420toNV12(src_YUV_I420.data, dst.data, w_img, h_img);
}

cv::Rect2f Utils::xywh_cxcywh(const cv::Rect2f& rect) {
    cv::Rect2f res;
    res.x = rect.x + rect.width/2;
    res.y = rect.y + rect.height/2;
    res.width = rect.width;
    res.height = rect.height;
    return res;
}

cv::Size2f Utils::letterbox_dim(cv::Size img_size, cv::Size box) {
    auto h = box.height, w = box.width;
    auto img_h = img_size.height, img_w = img_size.width;
    auto s = std::min(1.0f * w / img_w, 1.0f * h / img_h);
    return cv::Size2f(float(img_w * s), float(img_h * s));
}

cv::Mat Utils::letterbox_img(const cv::Mat &img, cv::Size input_size) {
    auto h = input_size.height, w = input_size.width;
    auto new_size = letterbox_dim(img.size(), input_size);
    auto new_h = new_size.height, new_w = new_size.width;

    static cv::Mat out = (cv::Mat::zeros(h, w, CV_8UC3) + 1) * 128;
    cv::resize(img,
               out({int((h - new_h) / 2), int((h - new_h) / 2 + new_h)},
                   {int((w - new_w) / 2), int((w - new_w) / 2 + new_w)}),
               {int(new_w), int(new_h)},
               0, 0, cv::INTER_CUBIC);
    return out;
}

void Utils::inv_letterbox_bbox(cv::Rect2f& bbox, cv::Size box_dim, cv::Size img_dim) {
    auto img_h = img_dim.height, img_w = img_dim.width;
    auto h = box_dim.height, w = box_dim.width;
    auto new_size = letterbox_dim(img_dim, box_dim);
    auto new_w = new_size.width, new_h = new_size.height;
    bbox.x += (-(w - new_w) / 2);
    bbox.x *= (1.0f * img_w / new_w);
    bbox.width *= (1.0f * img_w / new_w);

    bbox.y += (-(h - new_h) / 2);
    bbox.y *= (1.0f * img_h / new_h);
    bbox.height *= (1.0f * img_h / new_h);
}

void Utils::draw_bbox(cv::Mat &img, cv::Rect bbox,
                const std::string &label, const cv::Scalar &color) {
    cv::rectangle(img, bbox, color);
    if (!label.empty()) {
        Utils::draw_text(img, label, color, bbox.tl());
    }
}


void Utils::draw_text(cv::Mat &img, const std::string &str,
                const cv::Scalar &color, cv::Point pos, bool reverse) {
    auto t_size = cv::getTextSize(str, cv::FONT_HERSHEY_PLAIN, 1, 1, nullptr);
    cv::Point bottom_left, upper_right;
    if (reverse) {
        upper_right = pos;
        bottom_left = cv::Point(upper_right.x - t_size.width, upper_right.y + t_size.height);
    } else {
        bottom_left = pos;
        upper_right = cv::Point(bottom_left.x + t_size.width, bottom_left.y - t_size.height);
    }

    cv::rectangle(img, bottom_left, upper_right, color, -1);
    cv::putText(img, str, bottom_left, cv::FONT_HERSHEY_PLAIN, 1, cv::Scalar(255, 255, 255) - color);
}


cv::Scalar Utils::color_map(int64_t n) {
    auto bit_get = [](int64_t x, int64_t i) { return x & (1 << i); };

    int64_t r = 0, g = 0, b = 0;
    int64_t i = n;
    for (int64_t j = 7; j >= 0; --j) {
        r |= bit_get(i, 0) << j;
        g |= bit_get(i, 1) << j;
        b |= bit_get(i, 2) << j;
        i >>= 3;
    }
    return cv::Scalar(b, g, r);
}